/*
 * A class to implement KMeans Algorithm
 * Author : TuanNA
 * */

package kmeansclustering;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import java.util.Scanner;

public class KMeans {
	private int sumOfCluster;
	private String filePath;
	private int typeOfDistance;
	public float sumError;
        public float[][] data;
        public float[][] data2D;
        public float threshold = 100.0f;
	public int sumOfLine;
	public int sumOfColumn;
        public ArrayList<Cluster> clusters = new ArrayList<>();

        //***************************************
	public ArrayList<Point> points = new ArrayList<>();
	public ArrayList<Point> centroids = new ArrayList<>();
	
	/*
	 * Chú ý : load data ngay khi khởi tạo đối tượng
	 * khong can load them data nua
	 * 
	 */

	public KMeans(int sumOfCluster, String filePath) {
		this.sumOfCluster = sumOfCluster;
		this.filePath = filePath;
		this.sumOfLine = countLine(this.filePath);
		this.sumOfColumn = 24;
		try {
			this.data = this.loadData(); // phep gan lai. E Hem
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	// count line in a file
	public int countLine(String filePath) {
		int sumOfLine = 0;
		try {
			FileReader inputFile = new FileReader(this.filePath);
			BufferedReader bufferReader = new BufferedReader(inputFile);
			String line;
			while ((line = bufferReader.readLine()) != null) {
				sumOfLine += 1;
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return sumOfLine;
	}

	// load data from txt file
	public float[][] loadData() throws FileNotFoundException {
		Scanner input = new Scanner(new File(this.filePath));
//		int sumOfLine = countLine(this.filePath);
//		int sumOfColumn = 10;
		float[][] data = new float[sumOfLine][sumOfColumn];
		// System.out.println("sumOfLine" + sumOfLine);
		System.out.println("before");
		for (int i = 0; i < sumOfLine; i++) {
			for (int j = 0; j < sumOfColumn; j++)
				if (input.hasNextFloat()) {
					data[i][j] = (float) input.nextFloat();
				}
		}
		this.data = data;    // gan o day ? sao van con gan o ngoai ???

		// init points
                //*********************
                // point de lam gi ?
		for (int i = 0; i < sumOfLine; i++) {
			float[] property = new float[sumOfColumn];
			for (int j = 0; j < sumOfColumn; j++) {
				// property of a Point Object
				property[j] = this.data[i][j];
			}
			Point point = new Point(property, i); // init a Point object
			points.add(point);
		}

		return data;
	}

        //*****************************************************************
        // OK
	// random k centroid from dataset, id of centroid = id of cluster
	public ArrayList<Integer> randCentroid() {
//		int sumOfLine = countLine(this.filePath);
		ArrayList<Integer> centroids = new ArrayList<Integer>();
		Random random = new Random();
		while (centroids.size() < this.sumOfCluster) {
			int centroid = random.nextInt(sumOfLine);
			if (!centroids.contains(centroid)) {
				centroids.add(centroid);
			}
		}
		
		return centroids;
	}

	// init clusters from k centroid
	public ArrayList<Cluster> initClusters() throws FileNotFoundException {
            
		Iterator<Point> iterator = this.points.iterator();
		ArrayList<Integer> intCentroids = this.randCentroid(); // Arraylist store id of centroids
		ArrayList<Point> centroids = new ArrayList<>();
		
		for (int i = 0; i < this.sumOfCluster; i++) {
			// init some cluster(sumOfCluster)
			for (int j = 0; j < sumOfLine; j++) {
				// loop over matrix data to find the line have id = id of centroid
				if (j == intCentroids.get(i)) {
					float[] property = new float[24];
					for (int k = 0; k < sumOfColumn; k++) {
						property[k] = this.data[j][k];
					}
					Point centroid = new Point(property, j);
					centroids.add(centroid);
				}

			}
		}
		this.centroids = centroids;

		for (int i = 0; i < this.sumOfCluster; i++) {
			ArrayList<Point> pointOfCluster = new ArrayList<>();
			Cluster cluster = new Cluster(i, centroids.get(i), pointOfCluster);
			clusters.add(cluster);
		}
		this.clusters = clusters;
		return this.clusters;
	}

	// calculate Euclid Distance between 2 point
	public double calculateEuclidDistance(int[] point1, int[] point2) {
		double Sum = 0.0;
		for (int i = 0; i < point1.length; i++) {
			Sum = Sum + Math.pow((point1[i] - point2[i]), 2.0);
		}
		return Math.sqrt(Sum);
	}

	// Calculate sum of Error in each cluster=> tong binh phuong
	public float calculateSumError() {
		Iterator<Cluster> iterator = this.clusters.iterator();
		while (iterator.hasNext()) {
			this.sumError += iterator.next().calculateError();
		}
		return this.sumError;
	}

	public void clustering() throws FileNotFoundException {
		this.initClusters();    // k tra ve dung void => nhanh hon. 
		int loop = 10;          // Or de boolean ktra thuc hien dung hay k
		// While loop for clustering
		float deltaOfError = 1000.0f;
		while (loop > 0) {
                        // buoc dau tien, lam gi da co cum ma tinh loi ????
			float sumOfError = this.calculateSumError();
			System.err.println("idCluster");
			System.err.println("points size : " + this.points.size());
                        // points.size() => sao k gan bien ma cu tinh di tinh lai trong phep so sanh?
			for (int i = 0; i < points.size(); i++) {
				System.err.println("idCluster");
				int idCluster = points.get(i).setCluster(centroids);
				System.err.println("idCluster" + idCluster);
				for (int j = 0; j < clusters.size(); j++) {
					if (idCluster == j) {
						System.err.println("before add point to cluster");
						clusters.get(j).addPoints(points.get(i));
					}
				}
			}
			System.err.println("idCluster after");
			// step 3: recalculate centroid for all cluster

			for (int i = 0; i < clusters.size(); i++) {
				clusters.get(i).resetCentroid();
			}
			
			for (int i = 0; i < points.size(); i++) {
				int idCluster = points.get(i).setCluster(centroids);
				for (int j = 0; j < clusters.size(); j++) {
					if (idCluster == j) {
						clusters.get(j).addPoints(points.get(i));
					}
				}
			}

			Iterator<Cluster> iteratorCluster = this.clusters.iterator();
			System.out.println("size of clusters : " + this.clusters.size());
			while (iteratorCluster.hasNext()) {
				Cluster cluster = iteratorCluster.next();
				cluster.displayPoint();
			}

			// check end condition
			ArrayList<ArrayList<Integer>> listPointAnyCluster = new ArrayList<>();
			for (int i = 0; i < clusters.size(); i++) {
				listPointAnyCluster.add(clusters.get(i).getIdAllPoint());
			}
			
			System.out.println("Sum of Error : " + sumOfError);
			float deltaSumError = this.calculateSumError() - sumOfError;

			loop--;
		}
	}

	// Main function
	public static void main(String[] args) {
		KMeans KM = new KMeans(3, "D:\\dataset.txt");
		try {
			KM.clustering();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

class Dataset {
	public int sumOfLine;
	public int[][] data;

	public Dataset(int sumOfLine, int[][] data) {
		this.sumOfLine = sumOfLine;
		this.data = data;
	}
}